# 知识分享相关的序列化器

from rest_framework import serializers
from sa.serializers.accountSerializer import UserInfoSerializer
from sa.models import Article, User, ProductType, KnowledgeDomain, ArticleAttachment
# 验证文件扩展名
from django.core.validators import FileExtensionValidator
import os


class ArticleAttachmentSerializer(serializers.ModelSerializer):
    """ 文章附件序列化器 """
    created_at = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S", read_only=True)
    # 再增加两个字段（仅做序列化输出）
    # 模型中 FileField 或 ImageField字段，只返回数据库存储的相对路径, 不包含下载所需的干净文件名
    # serializers.SerializerMethodField(), 通过自定义的get方法，直接查询数据库
    file_url = serializers.SerializerMethodField()
    file_name = serializers.SerializerMethodField()  # 自定义字段，返回干净文件名

    class Meta:
        model = ArticleAttachment
        fields = '__all__'
        read_only_fields = ['id', 'created_at']

    """
    self.context 序列化器的 context 是一个字典，包含视图传递给序列化器的额外数据。
    默认情况下，DRF 会自动传递 request 和 view 到序列化器的 context 中（如果视图继承自 GenericAPIView 或 ModelViewSet）
    在DRF中 self.kwargs 是一个字典，包含了从 URL 路由中捕获的命名参数。
    """

    def get_file_url(self, obj):
        pass

    def get_file_name(self, obj):
        if obj.file:
            # os.path.basename 是 Python 的 os.path 模块中的一个实用函数，专门用于从文件路径中提取最后的文件名或目录名
            # 只要你在 Django 模型中使用 FileField 或 ImageField，上传的文件对象（obj.file）就一定会有 .name 属性
            return os.path.basename(obj.file.name)
        return None


class ArticleSerializer(serializers.ModelSerializer):
    """ 文章主体序列化器 """
    # 反序列化，使用id
    author = serializers.PrimaryKeyRelatedField(
        queryset=User.objects.all(), write_only=True,
        help_text='作者id，仅反序列化'
    )
    category = serializers.PrimaryKeyRelatedField(
        queryset=ProductType.objects.all(), write_only=True,
        many=True,
        help_text='产品类型id， 仅反序列化'
    )
    tags = serializers.PrimaryKeyRelatedField(
        queryset=KnowledgeDomain.objects.all(), write_only=True,
        many=True,
        help_text='标签（知识类型id）, 仅反序列化'
    )
    # ListField + FileField 处理多文件上传场景
    # serializers.ListField 处理列表类型的数据 FileField 只能处理单个文件，而 ListField(child=FileField()) 可以处理多个文件
    # child参数：指定列表中每个元素的序列化器类型
    attachments = serializers.ListField(
        child=serializers.FileField(
            max_length=100,  # FileField(max_length=100) 用于验证上传文件的文件名长度（包括扩展名）
            validators=[  # 添加验证器
                FileExtensionValidator(  # 验证文件扩展名
                    allowed_extensions=[
                        'pdf', 'doc', 'docx', 'xls', 'xlsx', 'jpg', 'png', 'csv', 'ppt', 'pptx'
                    ]
                )
            ]
        ),
        max_length=3,  # 限制最多3个文件
        write_only=True,
        required=False
    )

    # 序列化，返回对象（通过另一个序列化器）
    author_obj = UserInfoSerializer(source='author', read_only=True,
                                    help_text='作者对象，仅序列化输出')
    # 将 Article 模型的反向关联对象（即附件列表）序列化为 JSON, article.articleattachment_set.all()
    # source='attachments', 因为设置了 related_name='attachments'
    # DRF 会自动处理 article.attachments.all() 查询，这是 Django 的 ManyToManyField 或 RelatedManager 的标准行为。
    # 等效于：serializer = ArticleAttachmentSerializer(instance.attachments.all(), many=True)
    # 如果需要复杂查询，应改用 SerializerMethodField。
    attachment_list = ArticleAttachmentSerializer(source='attachments', many=True, read_only=True)
    # 序列化，通过 serializers.SerializerMethodField()，直接访问模型，需要自定义get_方法
    category_obj = serializers.SerializerMethodField(
        read_only=True,
        help_text="产品分类对象列表"
    )
    tags_obj = serializers.SerializerMethodField(
        read_only=True,
        help_text="知识领域对象列表"
    )
    created_at = serializers.SerializerMethodField()
    updated_at = serializers.SerializerMethodField()

    class Meta:
        model = Article
        fields = '__all__'
        read_only_fields = ['id', 'created_at', 'updated_at', 'views']
        extra_kwargs = {
            'title': {'required': True},
            'content': {'required': True},
            'attachments': {'required': False}
        }

    def get_category_obj(self, obj):
        return [
            {
                'id': item.id,
                'name': item.name
            } for item in obj.category.all()
        ]

    def get_tags_obj(self, obj):
        return [
            {
                'id': item.id,
                'name': item.name,
                'color': item.color
            } for item in obj.tags.all()
        ]

    def get_created_at(self, obj):
        return obj.created_at.strftime("%Y-%m-%d %H:%M:%S")

    def get_updated_at(self, obj):
        return obj.updated_at.strftime("%Y-%m-%d %H:%M:%S")

    def validate(self, attrs):
        title = attrs.get('title')
        # 1. 检查 title 是否存在（非 None）
        if not title:
            raise serializers.ValidationError({'title': '文章标题不能为空！'})
        # 2. 检查 title 是否为字符串
        if not isinstance(title, str):
            raise serializers.ValidationError({'title': '标题必须是字符串！'})
        # 3. 检查 title 是否为空或纯空格
        if not title.strip():
            raise serializers.ValidationError({'title': '标题不能仅为空格！'})

        attachments = attrs.get('attachments', [])
        max_size = 5 * 1024 * 1024
        for file in attachments:
            if file.size > max_size:
                raise serializers.ValidationError({
                    'attachments': f"文件 {file.name} 大小不能超过 5MB"
                })

        return attrs

    """
    为什么要重写 create 和 update 方法：
    Article 和 Attachment 的一对多关系：
    附件 (ArticleAttachment) 必须关联到一个已存在的文章 (Article),因此需要 先创建 Article，
    再用它的 ID 去创建 Attachment 记录。
    FormData 混合提交问题：
    前端通过 multipart/form-data 同时提交了表单字段（JSON）和二进制文件
    DRF 默认的 create() 无法自动处理这种嵌套关系+文件上传的特殊场景
    """

    def create(self, validated_data):
        # 先将附件剥离出去
        attachments_data = validated_data.pop('attachments', [])
        # 验证文件数量
        if len(attachments_data) > 3:
            raise serializers.ValidationError({
                'attachments': '最多上传3个附件'
            })
        # 验证文件大小
        for file in attachments_data:
            # print(file.size)
            if file.size > 5 * 1024 * 1024:
                raise serializers.ValidationError({
                    'attachments': f'文件{file.name}超过5MB限制'
                })
        # 创建文章主体实例, 执行父类方法
        article = super().create(validated_data)
        # 在文章实例存在的情况下再增加附件
        try:
            for single_file in attachments_data:
                ArticleAttachment.objects.create(
                    article=article,
                    file=single_file
                )
        except Exception as e:  # 如果发生任何异常（如文件存储失败、数据库错误等）
            article.delete()  # 删除已经创建的 article（回滚）
            raise serializers.ValidationError(f'附件保存失败: {str(e)}')
        # 再返回 article 文章，这样就在article对像里包含了附件
        return article

    def update(self, instance, validated_data):
        # 1.先处理删除
        delete_ids = self.context['request'].data.getlist('deletedAttachments', [])
        print('delete_ids:', delete_ids)
        if delete_ids:
            for attachment in instance.attachments.filter(id__in=delete_ids, article=instance):
                # 删除物理文件
                try:
                    if attachment.file and os.path.exists(attachment.file.path):
                        os.remove(attachment.file.path)
                except OSError as e:
                    print(f"文件删除失败（ID: {attachment.id}）: {e}")
                # 删除数据库
                attachment.delete()
        # 重新加载实例以获取最新状态,否则缓存机制导致已删除数据仍被计数
        # instance = Article.objects.get(id=instance.pk)
        instance.refresh_from_db()
        #  剩余数量 删除后的实际数量
        remaining_count = instance.attachments.count()

        # 2.处理新增附件
        # 先取出附件数据
        attachments_data = validated_data.pop('attachments', [])
        # 验证附件总数不超过3个（验证总数（剩余数量 + 新增数量））
        if remaining_count + len(attachments_data) > 3:
            raise serializers.ValidationError({
                'attachments': [f"附件总数不能超过3个（当前已有{remaining_count}个）"]
            })
        # 更新文章实例
        article = super().update(instance, validated_data)

        # 在文章实例存在的情况下再增加附件
        try:
            for single_file in attachments_data:
                ArticleAttachment.objects.create(
                    article=article,
                    file=single_file
                )
        except Exception as e:
            # 更新情况下，就算失败也不用删除文章，只记录错误
            raise serializers.ValidationError({
                'attachments': [f"附件更新失败：{str(e)}"]
            })
        # 再返回 article 文章，这样就在article对像里包含了附件
        return article
